package com.meizi.exception;

import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
import com.meizi.enums.ErrorCodeEnum;
import com.meizi.utils.Result;
import com.mysql.jdbc.exceptions.MySQLSyntaxErrorException;
import feign.FeignException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import javax.xml.bind.ValidationException;
import java.util.MissingFormatArgumentException;

/**
 * @author Administration
 * @date 2021/8/18 - 17:06 - 星期三
 * @project lambda
 * @package com.meizi.utils
 * @by GGbomd猪猪侠
 */
@Slf4j
@RestControllerAdvice
@Component
public class MyNewGlobalExceptionHandler {

    /**
     * @ExceptionHandler 匹配抛出自定义的异常类型 RenException
     */
    @ExceptionHandler(value = RenException.class)
    public Result renExceptionHandler(RenException e) {
        StackTraceElement[] stackTrace = e.getStackTrace();
        StackTraceElement stackTraceElement = stackTrace[0];
        log.error("异常msg:" + e.getMsg());
        log.error("stackTraceElement:" + stackTraceElement.getClassName() + ":" + stackTraceElement.getMethodName()
                + ":" + stackTraceElement.getLineNumber());
        return new Result().error(e.getCode(), e.getMsg());
    }

    /**
     * 空指针异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = NullPointerException.class)
    public Result nullPointerExceptionHandler(NullPointerException e) {
        log.error(e.getMessage());
        return new Result().error(ErrorCodeEnum.NOT_NULL.getKey(), "数据不能为空");
    }

    /**
     * 数组下标越界异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = IndexOutOfBoundsException.class)
    public Result nullIndexOutOfBoundsException(IndexOutOfBoundsException e) {
        log.error(e.getMessage());
        return new Result().error("停止遍历数组");
    }

    /**
     * sql语句语法错误
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = MySQLSyntaxErrorException.class)
    public Result mySQLSyntaxErrorExceptionHandler(MySQLSyntaxErrorException e) {
        log.error(e.getMessage());
        return new Result().error(ErrorCodeEnum.FIELD_IS_MISSING.getKey());
    }

    /**
     * 无法识别的属性 专门的JsonMappingException
     * 子类专门用于指示由于遇到无法映射到Object属性（通过getter，构造函数参数或字段）的JSON属性而引起的问题。
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = UnrecognizedPropertyException.class)
    public Result unrecognizedPropertyExceptionHandler(UnrecognizedPropertyException e) {
        log.error(e.getMessage());
        return new Result().error(ErrorCodeEnum.NOT_NULL.getKey());
    }

    /**
     * 计算数据错误
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = ArithmeticException.class)
    public Result arithmeticExceptionHandler(ArithmeticException e) {
        log.error(e.getMessage());
        return new Result().error(ErrorCodeEnum.INTERNAL_SERVER_ERROR.getKey(), "数据计算错误");
    }

    /**
     * feign异常处理
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = FeignException.class)
    public Result feignExceptionHandler(FeignException e) {
        log.error(e.getMessage());
        return new Result().error(ErrorCodeEnum.SERVICE_CALL_ERROR.getKey());
    }

    /**
     * javax.validation 单个对象返回两条数据 缺少格式参数异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler({ MissingFormatArgumentException.class })
    public Result handleMissingFormatArgumentException(MissingFormatArgumentException e) {
        log.warn("[handleMissingFormatArgumentException] 返回多条数据错误：", e);
        return new Result().error("缺少格式参数异常");
    }

    /**
     * 相同主键值重复异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(DuplicateKeyException.class)
    public Result handleDuplicateKeyException(DuplicateKeyException e) {
        log.error(e.getMessage());
        return new Result().error(ErrorCodeEnum.DB_RECORD_EXISTS.getKey());
    }

    /**
     * @ExceptionHandler 匹配抛出未定义的异常类型 Exception
     */
    @ExceptionHandler(value = Exception.class)
    public Result exceptionHandler(Exception e) {
        Result result = new Result();
        try {
            result.setCode(ErrorCodeEnum.INTERNAL_SERVER_ERROR.getKey());
            result.setMsg("系统繁忙，请稍后再试。");
            StackTraceElement[] stackTrace = e.getStackTrace();
            StackTraceElement stackTraceElement = stackTrace[0];
            log.error("未知错误:className = " + stackTraceElement.getClassName() + "&fileName="
                    + stackTraceElement.getFileName() + "&methodName=" + stackTraceElement.getMethodName()
                    + "&lineNumber=" + stackTraceElement.getLineNumber()
                    + "&message=" + e.getMessage());
//			result.setData("error print: " + e);
        } catch (Exception exception) {
            exception.printStackTrace();
            result.setCode(500);
            result.setMsg("系统繁忙，请稍后再试。");
        }
        return result;
    }

    /**
     * 缺少servlet请求参数抛出的异常
     *
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({ MissingServletRequestParameterException.class })
    public Result handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
        log.warn("[handleMissingServletRequestParameterException] 参数错误: " + e.getParameterName());
        return new Result().error(ErrorCodeEnum.PARAMS_GET_ERROR.getKey());
    }

    /**
     * 请求参数不能正确读取解析时，抛出的异常，比如传入和接受的参数类型不一致
     *
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler({ HttpMessageNotReadableException.class })
    public Result handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
        log.warn("[handleHttpMessageNotReadableException] 参数解析失败：", e);
        return new Result().error(ErrorCodeEnum.PARAMS_RESOLUTION_FAILED.getKey());
    }

    /**
     * 请求参数无效抛出的异常
     *
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({ MethodArgumentNotValidException.class })
    public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        BindingResult result = e.getBindingResult();
        String message = getBindResultMessage(result);
        log.warn("[handleMethodArgumentNotValidException] 参数验证失败：" + message);
        return new Result().error(ErrorCodeEnum.PARAMS_VERIFICATION_FAILED.getKey());
    }

    private String getBindResultMessage(BindingResult result) {
        FieldError error = result.getFieldError();
        String field = error != null ? error.getField() : "空";
        String code = error != null ? error.getDefaultMessage() : "空";
        return String.format("%s:%s", field, code);
    }

    /**
     * 方法请求参数类型不匹配异常
     *
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({ MethodArgumentTypeMismatchException.class })
    public Result handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
        log.warn("[handleMethodArgumentTypeMismatchException] 方法参数类型不匹配异常: ", e);
        return new Result().error(ErrorCodeEnum.INTERNAL_SERVER_ERROR.getKey());
    }

    /**
     * javax.validation 下校验参数时抛出的异常
     *
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({ ValidationException.class })
    public Result handleValidationException(ValidationException e) {
        log.warn("[handleValidationException] 参数验证失败：", e);
        return new Result().error(ErrorCodeEnum.PARAMS_VERIFICATION_FAILED.getKey());
    }
}
